///////////////////////////////////////////////////////////////////////////////
//
// risonanza.c - acquire data for SNS Lab Fis acoustic resonance experience
// 
// 1st release: A. Lusiani <a.lusiani@sns.it> April 2003
//
// this program includes parts from poll.c, Part of Comedilib,
// Copyright (c) 1999,2000 David A. Schleef <ds@schleef.org>
// poll.c may be freely modified, distributed, and combined with
// other software, as long as proper attribution is given in the
// source code.
//

///////////////////////////////////////////////////////////////////////////////
//
// Modifications:
//
//

///////////////////////////////////////////////////////////////////////////////
//
// headers
//

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <comedilib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <string.h>
#include <sys/time.h>
#include <math.h>

///////////////////////////////////////////////////////////////////////////////
//
// function declarations
//

void dump_cmd(FILE *out,comedi_cmd *cmd);
void prepare_nonblocking_input(FILE* input);
void prepare_ai_scan(comedi_t *cifh,
		     int subdev,
		     comedi_cmd *cmdp,
		     unsigned int triggerType,
		     unsigned int sampleRate,
		     unsigned int scanType,
		     unsigned int scanNum,
		     unsigned int *chanList,
		     unsigned int chanListLen
		     );
void do_ai_scan(comedi_t *dev, int subdev, comedi_cmd *cmd);

///////////////////////////////////////////////////////////////////////////////
//
// variable  declarations
//

comedi_t *device;

const char* 		COMEDI_DEV = "/dev/comedi0";
//--- comedi sub-devices for Analog Input and Output
int			AI_SUBDEV;
int			AO_SUBDEV;

//--- buffer size for streaming data out of the driver
const unsigned int	COMEDI_BUFSIZ =	(4*1024*1024);

//--- Analog Input definitions
const unsigned int 	WAVEGEN_AI_CHAN=0;
const unsigned int 	MICEND_AI_CHAN=1;
const unsigned int 	MICMID_AI_CHAN=2;
const unsigned int 	TEMP1_AI_CHAN=3;
const unsigned int 	TEMP2_AI_CHAN=4;
const unsigned int 	TEMP3_AI_CHAN=5;
const unsigned int 	TEMP4_AI_CHAN=6;

//--- analog input channel list for DAQ
#define AISCAN_BEG	(0)
#define AISCAN_NUM	(3)
unsigned int aiScanList[AISCAN_NUM];

//--- Analog Input voltage range
const float 		AI_VMIN = -10;
const float 		AI_VMAX = 10;

//--- select analog in voltage reference from GROUND
const unsigned int	AI_AREF = AREF_GROUND;

//--- rate at which the input is sampled, 100 kHz
const double		AISCAN_RATE = 50000;
#define 		AISCAN_SAMPLES (1000)

//--- Analog Out definition
const unsigned int 	FREQTUNE_AO_CHAN = 0;

//--- minimum Analog Output value for smallest frequency
double 			FREQTUNE_MIN = -0.015;
//--- maximum Analog Output value for largest frequency
double			FREQTUNE_MAX = 0.015;
//--- number of frequency steps minus one
const unsigned int	FREQTUNE_NUM = 10;

//--- select Analog Output voltage reference from GROUND
const unsigned int	AO_AREF = AREF_GROUND;

//--- buffer size where reading from comedi device
#define BUFSIZE		(AISCAN_SAMPLES*AISCAN_NUM)
//--- buffer for ni_pcimio Analog Input driver (16-bit unsigned samples)
sampl_t buf[BUFSIZE];

////////////////////////////////////////////////////////////////////////////////
//
// code
//

int main(int argc, char *argv[])
{
  comedi_t *cifh;
  comedi_cmd cmd;
  int i;
  int rc;
  unsigned long pageSize;
  unsigned int pageNum;
  lsampl_t maxDataAi;
  lsampl_t maxDataAo;
  lsampl_t data;
  double minVoltsAi, maxVoltsAi;
  double minVoltsAo, maxVoltsAo;
  unsigned int rangetAi;
  comedi_range* rangepAi;
  unsigned int rangetAo;
  comedi_range* rangepAo;
  
  //--- endpoints of acquisition are valid numbers
  rc = comedi_set_global_oor_behavior(COMEDI_OOR_NUMBER);

  cifh = comedi_open(COMEDI_DEV);
  if(!cifh){
    perror(COMEDI_DEV);
    exit(1);
  }

  //--- get virtual memory page size
  pageSize = sysconf(_SC_PAGE_SIZE);
  //--- get minimum number of pages needed for requested buffer size
  pageNum = (COMEDI_BUFSIZ+1)/pageSize;
  //--- set comedi device streaming buffer size
  rc = comedi_set_buffer_size(cifh, AI_SUBDEV, pageNum*pageSize);
  if(rc<0){
    fprintf(stderr, "comedi_set_buffer_size: %s\n",strerror(errno));
    exit(1);
  }else{
    fprintf(stderr, "buffer size set to %d\n", rc);
  }

  //--- find which subdevice offers Analog Input
  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_AI, 0);
  if (rc<0){
    comedi_perror("comedi_find_device AI");
    exit(1);
  }
  AI_SUBDEV = rc;

  rc = comedi_find_subdevice_by_type(cifh, COMEDI_SUBD_AO, 0);
  if (rc<0){
    comedi_perror("comedi_find_device AO");
    exit(1);
  }
  AO_SUBDEV = rc;

  //--- find AI range for -10 Volt .. 10 Volt
  rc = comedi_find_range(cifh, AI_SUBDEV,
			 WAVEGEN_AI_CHAN, UNIT_volt, AI_VMIN, AI_VMAX);
  if (rc<0){
    comedi_perror("comedi_find_range");
    exit(1);
  }
  rangetAi = rc;

  //--- get ptr to range struct
  rangepAi = comedi_get_range(cifh, AI_SUBDEV, WAVEGEN_AI_CHAN, rangetAi);
  //--- get maximum possible sample for AI
  maxDataAi = comedi_get_maxdata(cifh, AI_SUBDEV, WAVEGEN_AI_CHAN);
  minVoltsAi = comedi_to_phys(0, rangepAi, maxDataAi);
  maxVoltsAi = comedi_to_phys(maxDataAi, rangepAi, maxDataAi);

  fprintf(stderr, "Comedi offers AI range n. %d, %f .. %f\n",
	 rangetAi, minVoltsAi, maxVoltsAi);

  //--- build channel list for DAQ scan of microphones
  for(i=0;i<AISCAN_NUM;i++){
    aiScanList[i] = CR_PACK(AISCAN_BEG+i, rangetAi, AI_AREF);
  }

  //--- find AO range for -10 Volt .. 10 Volt
  rc = comedi_find_range(cifh, AO_SUBDEV,
			 FREQTUNE_AO_CHAN, UNIT_volt,
			 FREQTUNE_MIN, FREQTUNE_MAX);
  if (rc<0){
    comedi_perror("comedi_find_range");
    exit(1);
  }
  rangetAo = rc;

  //--- get ptr to range struct
  rangepAo = comedi_get_range(cifh, AO_SUBDEV, FREQTUNE_AO_CHAN, rangetAo);
  //--- get maximum possible sample for AO
  maxDataAo = comedi_get_maxdata(cifh, AO_SUBDEV, FREQTUNE_AO_CHAN);
  minVoltsAo = comedi_to_phys(0, rangepAo, maxDataAo);
  maxVoltsAo = comedi_to_phys(maxDataAo, rangepAo, maxDataAo);

  fprintf(stderr, "Comedi offers AO range n. %d, %f .. %f\n",
	 rangetAo, minVoltsAo, maxVoltsAo);

  //--- read temperatures
  fprintf(stderr,"Temperatures:");
  {
    unsigned int iChan;
    lsampl_t data;
    
    for(iChan=TEMP1_AI_CHAN; iChan<=TEMP4_AI_CHAN; iChan++) {
      rc = comedi_data_read(cifh, AI_SUBDEV, iChan, rangetAi, AI_AREF, &data);
      if (rc<0){
	comedi_perror("comedi_read_data");
	exit(1);
      }
      fprintf(stderr," %6.2f", comedi_to_phys(data, rangepAi, maxDataAi));
    }
    fprintf(stderr,"\n");
  }

  //--- prepare AI scan for microphones' input
  prepare_ai_scan(cifh, AI_SUBDEV, &cmd, 
		  TRIG_NOW, AISCAN_RATE, TRIG_COUNT, AISCAN_SAMPLES,
		  aiScanList, AISCAN_NUM);
  
  //--- ask for non-blocking I/O
  fcntl(comedi_fileno(cifh),F_SETFL,O_NONBLOCK);

  //--- prepare non blocking input from stdin for DAQ scan
  // prepare_nonblocking_input(stdin);

  {
    unsigned int iFreq;

    for (iFreq = 0; iFreq <= FREQTUNE_NUM; iFreq++) {
      data = comedi_from_phys(FREQTUNE_MIN+
			      ((double)iFreq)/FREQTUNE_NUM*
			      (FREQTUNE_MAX-FREQTUNE_MIN),
			      rangepAo, maxDataAo);
      fprintf(stderr,
	      "Preparing scan for frequency step %3d (0x%04x, %4d)\n",
	      iFreq, data, data-0x8000);
      rc = comedi_data_write(cifh, AO_SUBDEV, FREQTUNE_AO_CHAN,
			     rangetAo, AO_AREF, data);
      if (rc<0){
	comedi_perror("comedi_write_data");
	exit(1);
      }
      //--- wait 0.1 seconds for AO to settle 
      usleep(100000);

      do_ai_scan(cifh, AI_SUBDEV, &cmd);
    }
  }

  //--- leave frequency tunig DAC value at 0 Volts
  data = comedi_from_phys(0, rangepAo, maxDataAo);
  fprintf(stderr, "Leaving frequency at central value (0x%04x, %4d)\n",
	  data, data-0x8000);
  rc = comedi_data_write(cifh, AO_SUBDEV, FREQTUNE_AO_CHAN,
			 rangetAo, AO_AREF, data);
  if (rc<0){
    comedi_perror("comedi_write_data");
    exit(1);
  }

  return 0;
}

//
// prepare_ai_scan
//
// prepare comedi command for Analog Input scan
//
  
void prepare_ai_scan(comedi_t *cifh,
		     int subdev,
		     comedi_cmd *cmdp,
		     unsigned int triggerType,
		     unsigned int sampleRate,
		     unsigned int scanType,
		     unsigned int scanNum,
		     unsigned int *chanList,
		     unsigned int chanListLen
		     )
{
  int rc;

  //--- clear cmd struct
  memset(cmdp, 0, sizeof(*cmdp));
  
  /* the subdevice that the command is sent to */
  cmdp->subdev = 	subdev;
  
  /* flags */
  cmdp->flags =		0;
  
  /* each event requires a trigger, which is specified
     by a source and an argument.  For example, to specify
     an external digital line 3 as a source, you would use
     src=TRIG_EXT and arg=3. */
  
  /* In this case, we specify using TRIG_NOW to start
   * acquisition immediately when the command is issued.  
   * The argument of TRIG_NOW is "number of nsec after
   * NOW", but no driver supports it yet.  Also, no driver
   * currently supports using a start_src other than
   * TRIG_NOW.  */
  cmdp->start_src =		triggerType;
  cmdp->start_arg =		0;
  
  /* The timing of the beginning of each scan is controlled
   * by scan_begin.  TRIG_TIMER specifies that scan_start
   * events occur periodically at a rate of scan_begin_arg
   * nanoseconds between scans. */
  cmdp->scan_begin_src =	TRIG_TIMER;
  cmdp->scan_begin_arg =	((unsigned int)1e9)/sampleRate;
  
  /* The timing between each sample in a scan is controlled
   * by convert.  Like above, TRIG_TIMER specifies that
   * convert events occur periodically at a rate of convert_arg
   * nanoseconds between scans. */
  cmdp->convert_src =	TRIG_TIMER;
  cmdp->convert_arg =	0;
  
  /* The end of each scan is almost always specified using
   * TRIG_COUNT, with the argument being the same as the
   * number of channels in the chanlist.  You could probably
   * find a device that allows something else, but it would
   * be strange. */
  cmdp->scan_end_src =	TRIG_COUNT;
  cmdp->scan_end_arg =	chanListLen;	/* number of channels */
  
  /* The end of acquisition is controlled by stop_src and
   * stop_arg.  The src will typically be TRIG_COUNT or
   * TRIG_NONE.  Specifying TRIG_COUNT will stop acquisition
   * after stop_arg number of scans, or TRIG_NONE will
   * cause acquisition to continue until stopped using
   * comedi_cancel(). */
  cmdp->stop_src =		scanType;
  cmdp->stop_arg =		scanNum;
  
  /* the channel list determined which channels are sampled.
     In general, chanlist_len is the same as scan_end_arg.  Most
     boards require this.  */
  cmdp->chanlist =	chanList;
  cmdp->chanlist_len =	chanListLen;

  fprintf(stderr, "Assembled DAQ command\n");
  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh, cmdp);
  fprintf(stderr, "1st comedi_command_test rc: %d\n",rc);
  if(rc<0){
    fprintf(stderr, "1st comedi_command_test errno: %d\n",errno);
    comedi_perror("comedi_command_test");
    return;
  }
  
  fprintf(stderr, "DAQ command modified by comedi\n");
  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh,cmdp);
  fprintf(stderr,"2nd comedi_command_test rc: %d\n",rc);
  if (rc<0) {
    fprintf(stderr,"2nd comedi_command_test errno: %d\n",errno);
    comedi_perror("comedi_command_test");
    //--- comedi failure, no scan command possible, quit
    exit(1);
  }

  return;
}

//
// prepare_nonblocking_input
//
// setup terminal input for non-blocking input
//

void prepare_nonblocking_input(FILE* input)
{
  struct termios attr;

  //--- if input is a terminal, ask not to wait for newlines, no echo
  if (tcgetattr(fileno(input), &attr) == 0) {
    //--- echo off
    attr.c_lflag &= ~(ECHO);
    //--- do not wait for newline
    attr.c_lflag &= ~(ICANON);
    //--- read at least one char
    attr.c_cc[VMIN] = 1;
    // set new terminal attrs
    tcsetattr(fileno(input), TCSAFLUSH, &attr);
  }
}

//
// do_ai_scan
//
// scan Analog Inputs
//

void do_ai_scan(comedi_t *cifh, int subdev, comedi_cmd *cmdp)
{
  int rc;
  int go;
  int quit;
  unsigned int i;
  unsigned int j;
  fd_set rdset;
  struct timeval timeout;
  unsigned int scansTot;

  //--- start comedi Analog Input scan
  rc = comedi_command(cifh,cmdp);
  fprintf(stderr,"Start DAQ, rc= %d\n", rc);
  if (rc<0) {
    fprintf(stderr,"errno=%d\n",errno);
    comedi_perror("comedi_command");
    exit(1);
  }

  go = 1;
  quit = 0;
  scansTot = 0;

  //--- setup rdset structure to wait for comedi scans and keystrokes
  FD_ZERO(&rdset);
  FD_SET(comedi_fileno(cifh),&rdset);
  FD_SET(fileno(stdin),&rdset);

  //--- data acquisition loop (can be interrupted by <control-Y>
  while(go){
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    //--- wait untill either timeout or some data are available
    rc = select(comedi_fileno(cifh)+1,&rdset,NULL,NULL,&timeout);
    // fprintf(stderr,"select returned %d\n",rc);
    if (rc<0) {
      //--- error in select
      perror("select");
    } else if (rc==0) {
      //--- no data available before timeout
      //--- ask for available data even if driver buffer is not full
      // fprintf(stderr,"timeout, polling...\n");
      rc = comedi_poll(cifh,AI_SUBDEV);
      // fprintf(stderr,"poll returned %d\n",rc);
    } else if (FD_ISSET(fileno(stdin),&rdset)) {
      //--- standard input has some characters available
      unsigned char ch = fgetc(stdin);
      if (ch == 25 && go) {
	fprintf(stderr,"  --- <control-Y> received, quitting ... --- \n");
	quit = 1;
      } else {
	fprintf(stderr,"  --- Press <control-Y> to quit --- \n");
      }
    } else if (FD_ISSET(comedi_fileno(cifh),&rdset)) {
      //--- comedi file descriptor became ready
      // fprintf(stderr,"comedi file descriptor ready\n");
      //--- read available data
      rc = read(comedi_fileno(cifh), buf, sizeof(buf));
      // fprintf(stderr,"read returned %d\n",rc);
      if (rc<0) {
	if (errno==EAGAIN) {
	  //--- no data available, continue reading
	  fprintf(stderr, "Signal set but no data available, retrying\n");
	  perror("read");
	} else {
	  fprintf(stderr, "Error reading comedi device, quitting\n");
	  perror("read");
	  go = 0;
	}
      } else if (rc==0) {
	//--- if end of file reading comedi device then quit
	fprintf(stderr,"EOF, ending scan after reading %6d samples\n",
		scansTot);
	scansTot = 0;
	go = 0;
      } else {
	//--- read available data
	fprintf(stderr,"Read %d samples\n", rc);
	scansTot += rc;
	for(i=0; i<(unsigned int)rc/sizeof(sampl_t); i++) {
	  // fprintf(stderr,"%d\n",buf[i]);
	}
	for(j=0; j<3; j++) {
	  for(i=0; i<16; i++) {
	    fprintf(stderr,"%4x ",buf[i*3+j]);
	  }
	  fprintf(stderr,"\n");
	}
      }
    } else {
      /* unknown file descriptor became ready, should never happen */
      fprintf(stderr,"Unknown file descriptor ready\n");
    }
    if (quit) {
      go = 0;
    }
  }
}

//
// cmd_src
//
// decode source field of comedi command
//

char *cmd_src(int src,char *buf)
{
  buf[0]=0;
  
  if(src&TRIG_NONE)strcat(buf,"none|");
  if(src&TRIG_NOW)strcat(buf,"now|");
  if(src&TRIG_FOLLOW)strcat(buf, "follow|");
  if(src&TRIG_TIME)strcat(buf, "time|");
  if(src&TRIG_TIMER)strcat(buf, "timer|");
  if(src&TRIG_COUNT)strcat(buf, "count|");
  if(src&TRIG_EXT)strcat(buf, "ext|");
  if(src&TRIG_INT)strcat(buf, "int|");
#ifdef TRIG_OTHER
  if(src&TRIG_OTHER)strcat(buf, "other|");
#endif
  
  if(strlen(buf)==0){
    sprintf(buf,"unknown(0x%08x)",src);
  }else{
    buf[strlen(buf)-1]=0;
  }
  
  return buf;
}

//
// dump_cmd
//
// decode comedi command
//

void dump_cmd(FILE *out,comedi_cmd *cmd)
{
  char buf[100];
  
  fprintf(out,"--- begin comedi command\n");
  fprintf(out,"start:      %-8s %d\n",
	  cmd_src(cmd->start_src,buf),
	  cmd->start_arg);
  
  fprintf(out,"scan_begin: %-8s %d\n",
	  cmd_src(cmd->scan_begin_src,buf),
	  cmd->scan_begin_arg);
  
  fprintf(out,"convert:    %-8s %d\n",
	  cmd_src(cmd->convert_src,buf),
	  cmd->convert_arg);
  
  fprintf(out,"scan_end:   %-8s %d\n",
	  cmd_src(cmd->scan_end_src,buf),
	  cmd->scan_end_arg);
  
  fprintf(out,"stop:       %-8s %d\n",
	  cmd_src(cmd->stop_src,buf),
	  cmd->stop_arg);
  fprintf(out,"--- end comedi command\n");
}

